#include "gd32f3x0.h"
#include <stdio.h>
#include <stdlib.h>
#include "log.h"
#include "Systick_Ctrl.h"
#include "SPI_Ctrl.h"
#include "UART_Ctrl.h"
#include "UART_Com.h"
#include "UART_Upgrade.h"

uint8_t          gUpgradeCmd[UPGRADE_CMD_BUF_SIZE * 2];
UPGRADE_PACKET*  gpUpgradePacket = 0;
uint32_t         gFMCWriteLen = 0;

uint32_t TiDTV_ComputeCRC32Value(uint8_t* pData, int len)
{
#if 0
#define PCCOMM_CRC32_QUOTIENT 0x04C11DB7
    uint8_t        octet  = 0;
    uint32_t         result = 0;
    int                  i      = 0;
    int                  j      = 0;

    //Initialize to all ones
    result = 0xFFFFFFFF;

    for(i = 0; i < len; i++)
    {
        octet = *(pData++);
        for(j = 0; j < 8; j++)
        {
            if((octet >> 7) ^ (result >> 31))
            {
                result = ((result << 1) ^ PCCOMM_CRC32_QUOTIENT);
            }
            else
            {
                result = (result << 1);
            }
            octet <<= 1;
        }
    }

    //The user must be careful to write the four octets of the result
    //into the data part of the cell in the correct order
    //(that is bits 24-31 first and bits 0-7 last) irrespective of the
    //endian of the machine.
    return ~result;             /* The complement of the remainder */
#else
    uint32_t result = 0;
    int i;

    for(i = 0; i < len; i++)
    {
        result += *pData;
        pData++;
    }

    return result;
#endif
}

int UART_Upgrade_Check(uint32_t USARTx)
{
#define UPGRADE_CHECK_TIMES 5
    int CheckTimes = 0;
    uint8_t Response[3] = {'Y', 'E', 'S'};
    
    if(USARTx != USART0 && USARTx != USART1)
    {
        return -1;
    }

    LOG_PRINT(("Wait for upgrading...\r\n"));

    gUpgradeCmd[0] = 0;
    UART_RXDMAConfig(USARTx, gUpgradeCmd, UPGRADE_CMD_BUF_SIZE);
    UART_EnableDMARX(USARTx);

    /* Wait until USART RX DMA Channel Transfer Complete */
    while(UART_DMARXDone(USARTx) == RESET)
    {
        Systick_Delay(100);
        CheckTimes++;
        if(CheckTimes >= UPGRADE_CHECK_TIMES)
        {
            break;
        }
    }

    UART_DisableDMARX(USARTx);
    if(CheckTimes >= UPGRADE_CHECK_TIMES) {
        return -1;
    }

    for(CheckTimes = 0; CheckTimes < UPGRADE_CMD_BUF_SIZE; CheckTimes++)
    {
        if(gUpgradeCmd[CheckTimes] == 'C')
        {
            break;
        }
    }

    if(CheckTimes > (UPGRADE_CMD_BUF_SIZE - 6))
    {
        return -1;
    }
    
    if(gUpgradeCmd[CheckTimes]     == 'C' && gUpgradeCmd[CheckTimes + 1] == 'O' && gUpgradeCmd[CheckTimes + 2] == 'M' &&
       gUpgradeCmd[CheckTimes + 3] == 'C' && gUpgradeCmd[CheckTimes + 4] == 'O' && gUpgradeCmd[CheckTimes + 5] == 'M')
    {
        UPGRADE_LOG(("UART Shakehands successfully\r\n"));
        gFMCWriteLen = 0;
        /*Send the ACK Command*/
        UART_Send(USARTx, Response, 3);
        UART_Upgrade_Process(USARTx);
    }
    else if(gUpgradeCmd[CheckTimes]     == 'C' && gUpgradeCmd[CheckTimes + 1] == 'H' && gUpgradeCmd[CheckTimes + 2] == 'K' &&
            gUpgradeCmd[CheckTimes + 3] == 'C' && gUpgradeCmd[CheckTimes + 4] == 'H' && gUpgradeCmd[CheckTimes + 5] == 'K')
    {
        UART_Send(USARTx, Response, 3);
        Systick_Delay(500);
        UART_Upgrade_Verify(USARTx);
    }

    return 0;
}

void UART_Upgrade_Process(uint32_t USARTx)
{
    uint8_t Erase[5] = {'E', 'r', 'a', 's', 'e'};
    uint8_t Start[5] = {'S', 't', 'a', 'r', 't'};
    UART_MSG msg;
    uint32_t crc32, FlashWriteAddr = 0, NextIdx = 1;
    uint8_t* pData;
    int i, retry = 0;
    int upgradeOK = 1;

    gpUpgradePacket = (UPGRADE_PACKET*)(gComMsgBuf);

    Systick_Delay(1000);

    UPGRADE_LOG(("Erase flash: Start: 0x%X Size: %d pages\r\n", USER_APP_ADDR, USER_APP_PAGE_COUNT + USER_DATA_PAGE_COUT));
    UART_Send(USARTx, Erase, 5);
#ifdef USE_FMC_FLASH
    fmc_unlock();
    fmc_flag_clear(FMC_FLAG_END | FMC_FLAG_WPERR | FMC_FLAG_PGERR);
    for(i = 0; i < USER_APP_PAGE_COUNT + USER_DATA_PAGE_COUT; i++)
    {
        fmc_page_erase(USER_APP_ADDR + i * FMC_PAGE_SIZE);
        fmc_flag_clear(FMC_FLAG_END | FMC_FLAG_WPERR | FMC_FLAG_PGERR);
    }
#endif

    Systick_Delay(500);

    UART_RXDMAConfig(USARTx, (uint8_t*)gpUpgradePacket, sizeof(UPGRADE_PACKET));
    UART_EnableDMARX(USARTx);
    msg.MSG[0] = 'N'; msg.MSG[1] = 'E'; msg.MSG[2] = 'X'; msg.MSG[3] = 'T';

    UART_Send(USARTx, Start, 5);

    while(1)
    {
        while(UART_DMARXDone(USARTx) == RESET)
        {
            Systick_Delay(100);
        }

        UART_DisableDMARX(USARTx);
        crc32 = TiDTV_ComputeCRC32Value((uint8_t*)gpUpgradePacket, sizeof(UPGRADE_PACKET) - sizeof(uint32_t));
        UPGRADE_LOG(("Upgrade Packet: %d %d %d %d\r\n", gpUpgradePacket->curBlockIdx, gpUpgradePacket->totalBlocks,
                                                        gpUpgradePacket->BlockSize, gpUpgradePacket->crc32));
        UPGRADE_LOG(("CRC32: %d\r\n", crc32));
        if(crc32 == gpUpgradePacket->crc32 && gpUpgradePacket->curBlockIdx == NextIdx)
        {
            UPGRADE_LOG(("Got No. %d good packet\r\n", NextIdx));
            pData = gpUpgradePacket->data;
#ifdef USE_FMC_FLASH
            i = 0;
            FlashWriteAddr = USER_APP_ADDR + gFMCWriteLen;
            UPGRADE_LOG(("Will program Addr: 0x%X\r\n", FlashWriteAddr));
            while(i < UPGRADE_BUF_SIZE)
            {
                /* Write word into flash memory */
                if(fmc_word_program(FlashWriteAddr, *(__IO uint32_t *)(pData)) != FMC_READY)
                {
                    break;
                }
                pData += 4;
                FlashWriteAddr += 4;
                i += 4;
            }
#endif
            if(i == UPGRADE_BUF_SIZE)
            {
                UPGRADE_LOG(("Program No. %d packet OK\r\n", NextIdx));
                /*Next packet*/
                retry = 0;
                NextIdx++;
                gFMCWriteLen += i;
            }
        }
        msg.msgVal = NextIdx;
        retry++;

       if (retry >= MAX_RETRY_TIMES) {
            UPGRADE_LOG(("UART Upgrade ERROR!\r\n"));
#ifdef USE_FMC_FLASH
            fmc_flag_clear(FMC_FLAG_END | FMC_FLAG_WPERR | FMC_FLAG_PGERR);
            fmc_page_erase(USER_APP_ADDR);
            fmc_flag_clear(FMC_FLAG_END | FMC_FLAG_WPERR | FMC_FLAG_PGERR);
#endif
            msg.MSG[0] = 'E'; msg.MSG[1] = 'R'; msg.MSG[2] = 'R'; msg.MSG[3] = 'O';
            msg.msgVal = 0;
            UART_Send(USARTx, (uint8_t *)&msg, sizeof(msg));
            upgradeOK = 0;
            break;
        } 

        UART_RXDMAConfig(USARTx, (uint8_t*)gpUpgradePacket, sizeof(UPGRADE_PACKET));
        UART_EnableDMARX(USARTx);
        if(gpUpgradePacket->curBlockIdx != gpUpgradePacket->totalBlocks && 
           gFMCWriteLen < USER_APP_SIZE)
        {
            UART_Send(USARTx, (uint8_t *)&msg, sizeof(msg));
        }
        else
        {
            UPGRADE_LOG(("UART Upgrade DONE!\r\n"));
            msg.MSG[0] = 'D'; msg.MSG[1] = 'O'; msg.MSG[2] = 'N'; msg.MSG[3] = 'E';
            msg.msgVal = 0;
            UART_Send(USARTx, (uint8_t *)&msg, sizeof(msg));
            break;
        }
    }

#ifdef USE_FMC_FLASH
    fmc_lock();
#endif
    gpUpgradePacket = 0;
    
    if (upgradeOK) {
        UPGRADE_LOG(("UART Upgrade OK! Will auto reboot!\r\n\r\n"));
        Systick_Delay(1000);
        iap_load_app(USER_APP_ADDR);
    }

    while (upgradeOK) {
        /*Waiting for Reboot*/
        Systick_Delay(100);
    }
}

void UART_Upgrade_Verify(uint32_t USARTx)
{
#ifdef USE_FMC_FLASH
    uint32_t flashReadAddr = USER_APP_ADDR;
    uint32_t readlen = 0;
#endif
    uint32_t *pBuffer = 0;

    gpUpgradePacket = malloc(sizeof(UPGRADE_PACKET));
    if(gpUpgradePacket == 0)
    {
        return;
    }

    pBuffer = (uint32_t*)(gpUpgradePacket->data);
    while(flashReadAddr < (USER_APP_ADDR + USER_APP_SIZE))
    {
#ifdef USE_FMC_FLASH
        readlen = 0;
        while(readlen < (UPGRADE_BUF_SIZE / 4))
        {
            *(pBuffer + readlen) = *(((uint32_t *)flashReadAddr) + readlen);
            readlen++;
        }
#endif
        flashReadAddr += UPGRADE_BUF_SIZE;
        UART_Send(USARTx, (uint8_t*)pBuffer, UPGRADE_BUF_SIZE);
        Systick_Delay(10);
    }

    free(gpUpgradePacket);
    gpUpgradePacket = 0;
}
